﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DomainDriver.CommonUtilities.Reflectors;

namespace DomainDriver.CommonUtilities.Collections
{
    public static class CollectionUtilities
    {
        public static bool AreUnorderedCollectionsEqual<T>(ICollection<T> firstCollection, ICollection<T> secondCollection)
        {
            if (firstCollection == null)
            { throw new ApplicationException(); }
            if (secondCollection == null)
            { throw new ApplicationException(); }

            if (firstCollection.Count != secondCollection.Count)
            { return false; }

            foreach (T element in firstCollection)
            {
                if (!secondCollection.Contains(element))
                { return false; }
            }
            return true;
        }

        public static bool AreUnorderedDictionariesEqual<K, T>(IDictionary<K, T> firstDictionary, IDictionary<K, T> secondDictionary)
        {
            if (firstDictionary == null)
            { throw new ApplicationException(); }
            if (secondDictionary == null)
            { throw new ApplicationException(); }

            if (!AreUnorderedCollectionsEqual<K>(firstDictionary.Keys, secondDictionary.Keys))
            { return false; }

            foreach (K key in firstDictionary.Keys)
            {
                T firstElement = firstDictionary[key];
                T secondElement = secondDictionary[key];

                if (!firstElement.Equals(secondElement))
                { return false; }
            }
            return true;
        }

        public static IDictionary<KEY, KEYED_OBJECT> ConvertCollectionOfKeyedObjects<KEY, KEYED_OBJECT>(ICollection<KEYED_OBJECT> keyedObjects)
            where KEYED_OBJECT : IKeyedObject<KEY>
        {
            if (keyedObjects == null)
            { throw new ApplicationException(); }

            Dictionary<KEY, KEYED_OBJECT> dictionary = new Dictionary<KEY, KEYED_OBJECT>();
            foreach (KEYED_OBJECT keyValueObject in keyedObjects)
            {
                dictionary.Add(keyValueObject.Key, keyValueObject);
            }
            return dictionary;
        }

        public static int Convert(bool[] bools)
        {
            int numberOfBools = 32;
            int bits = 0;

            for (int boolIndex = 0; boolIndex < numberOfBools; boolIndex++)
            {
                int bitMask = (1 << boolIndex);
                if (bools[boolIndex])
                {
                    bits = bits | bitMask;
                }
                bool value = (bitMask == (bitMask & bits));
                bools[boolIndex] = value;
            }

            return bits;
        }

        public static bool[] Convert(int bits)
        {
            int numberOfBits = 32;
            bool[] bools = new bool[numberOfBits];

            for (int bitIndex = 0; bitIndex < numberOfBits; bitIndex++)
            {
                int bitMask = (1 << bitIndex);
                bool value = (bitMask == (bitMask & bits));
                bools[bitIndex] = value;
            }

            return bools;
        }

        #region GetMinValue()

        public static Nullable<T> GetMinValue<T>(ICollection<T> collection)
            where T : struct, IComparable<T>
        {
            Nullable<T> minValue = null;
            foreach (T value in collection)
            {
                if (minValue.HasValue)
                {
                    if (value.CompareTo(minValue.Value) == -1)
                    { minValue = value; }
                }
                else
                { minValue = value; }
            }
            return minValue;
        }

        public static Nullable<T> GetMinValue<T>(ICollection<T> collection, IComparer<T> comparer)
            where T : struct
        {
            Nullable<T> minValue = null;
            foreach (T value in collection)
            {
                if (minValue.HasValue)
                {
                    if (comparer.Compare(value, minValue.Value) == -1)
                    { minValue = value; }
                }
                else
                { minValue = value; }
            }
            return minValue;
        }

        public static Nullable<T_OUT> GetMinValue<T_OUT, T_IN>(ICollection<T_IN> collection, Expression<Func<T_IN, T_OUT>> expression)
            where T_OUT : struct, IComparable<T_OUT>
        {
            string propertyName = ClassReflector.GetPropertyName<T_IN, T_OUT>(expression);
            return GetMinValue<T_OUT, T_IN>(collection, propertyName);
        }

        public static Nullable<T_OUT> GetMinValue<T_OUT, T_IN>(ICollection<T_IN> collection, string propertyName)
            where T_OUT : struct, IComparable<T_OUT>
        {
            Type typeIn = typeof(T_IN);
            Type typeOut = typeof(T_OUT);
            PropertyInfo propertyInfo = typeIn.GetProperty(propertyName);

            if (propertyInfo == null)
            { throw new ApplicationException(); }
            if (!propertyInfo.PropertyType.Equals(typeOut))
            { throw new ApplicationException(); }

            List<T_OUT> propertyValues = new List<T_OUT>();
            foreach (T_IN input in collection)
            {
                propertyValues.Add((T_OUT)propertyInfo.GetValue(input, null));
            }

            return GetMinValue<T_OUT>(propertyValues);
        }

        #endregion

        #region GetMaxValue()

        public static Nullable<T> GetMaxValue<T>(ICollection<T> collection)
            where T : struct, IComparable<T>
        {
            Nullable<T> maxValue = null;
            foreach (T value in collection)
            {
                if (maxValue.HasValue)
                {
                    if (value.CompareTo(maxValue.Value) == 1)
                    { maxValue = value; }
                }
                else
                { maxValue = value; }
            }
            return maxValue;
        }

        public static Nullable<T> GetMaxValue<T>(ICollection<T> collection, IComparer<T> comparer)
            where T : struct
        {
            Nullable<T> maxValue = null;
            foreach (T value in collection)
            {
                if (maxValue.HasValue)
                {
                    if (comparer.Compare(value, maxValue.Value) == 1)
                    { maxValue = value; }
                }
                else
                { maxValue = value; }

            }
            return maxValue;
        }

        public static Nullable<T_OUT> GetMaxValue<T_OUT, T_IN>(ICollection<T_IN> collection, Expression<Func<T_IN, T_OUT>> expression)
            where T_OUT : struct, IComparable<T_OUT>
        {
            string propertyName = ClassReflector.GetPropertyName<T_IN, T_OUT>(expression);
            return GetMaxValue<T_OUT, T_IN>(collection, propertyName);
        }

        public static Nullable<T_OUT> GetMaxValue<T_OUT, T_IN>(ICollection<T_IN> collection, string propertyName)
            where T_OUT : struct, IComparable<T_OUT>
        {
            Type typeIn = typeof(T_IN);
            Type typeOut = typeof(T_OUT);
            PropertyInfo propertyInfo = typeIn.GetProperty(propertyName);

            if (propertyInfo == null)
            { throw new ApplicationException(); }
            if (!propertyInfo.PropertyType.Equals(typeOut))
            { throw new ApplicationException(); }

            List<T_OUT> propertyValues = new List<T_OUT>();
            foreach (T_IN input in collection)
            {
                propertyValues.Add((T_OUT)propertyInfo.GetValue(input, null));
            }

            return GetMaxValue<T_OUT>(propertyValues);
        }

        #endregion
    }
}